﻿using Bot.Salesforce.Model;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Bot.Salesforce
{
    public class SalesforceService : ISalesforceService
    {
        private readonly IWebRequestService _webRequestService;
        private readonly ILogger<SalesforceService> _logger;

        public SalesforceService(IWebRequestService webRequestService,
            ILogger<SalesforceService> logger)
        {
            _webRequestService = webRequestService;
            _logger = logger;
        }

        public async Task<SalesforceResponse> CreateSObjectAsync(string sobject, string content, bool ifUpdateDup, string host, string refreshToken)
        {
            var response = await CreateAsync(sobject, content, host, refreshToken);
            var salesforceResponse = ConvertToSalesforceResponse(response);
            if (salesforceResponse.IsSuccess)
            {
                var createRecordSuccess = JsonConvert.DeserializeObject<CreateRecordSuccess>(response);
                salesforceResponse.Payload = createRecordSuccess.id;
            }
            else
            {
                if (ifUpdateDup
                    && salesforceResponse.ErrorCode == "DUPLICATES_DETECTED")
                {
                    var id = GetDupResourceId(response, sobject, salesforceResponse);
                    if (string.IsNullOrEmpty(id))
                    {
                        salesforceResponse.ErrorMessage += ", but not find duplicate resource";
                    }
                    else
                    {
                        response = await UpdateAsync(sobject, id, content, host, refreshToken);
                        if (string.IsNullOrEmpty(response))
                        {
                            salesforceResponse.IsSuccess = true;
                            salesforceResponse.Payload = id;
                        }
                        else
                        {
                            salesforceResponse = ConvertToSalesforceResponse(response);
                        }
                    }
                }
            }

            return salesforceResponse;
        }

        public async Task<SalesforceResponse> GetSalesforceFieldsAsync(string sobject, string host, string refreshToken)
        {
            var response = await _webRequestService.SendAsync(host, $"/services/data/v52.0/sobjects/{sobject}/describe/", refreshToken, Enum.EnumHttpMethod.Get);
            var sobjectResponse = JsonConvert.DeserializeObject<SobjectDescribeResponse>(response);
            return new SalesforceResponse()
            {
                IsSuccess = true,
                Payload = ConvertToSalesforceFields(sobjectResponse),
            };             
        }

        public async Task<SalesforceResponse> GetSalesforceObjectsAsync(string host, string refreshToken)
        {
            var response = await _webRequestService.SendAsync(host, "/services/data/v52.0/sobjects/", refreshToken, Enum.EnumHttpMethod.Get);
            var salesforceResponse = ConvertToSalesforceResponse(response);
            if (salesforceResponse.IsSuccess)
            {
                var sobjectResponse = JsonConvert.DeserializeObject<SobjectResponse>(response);
                salesforceResponse.Payload = ConvertToSalesforceObjects(sobjectResponse);
            }
            return salesforceResponse;
        }

        public async Task<SalesforceResponse> QueryAsync(string q, string host, string refreshToken)
        {
            var response = await ExecSoqlAsync(q, host, refreshToken);
            var salesforceResponse = ConvertToSalesforceResponse(response);
            if (salesforceResponse.IsSuccess)
            {
                var queryResponse = JsonConvert.DeserializeObject<QueryResponse>(response);
                queryResponse = RemoveRecordAttributes(queryResponse);
                salesforceResponse.Payload = queryResponse.records;
            }
            return salesforceResponse;
        }

        public async Task<SalesforceResponse> SearchAsync(SearchInput input, string host, string refreshToken)
        {
            var q = BuildSoql(input);
            return await QueryAsync(q, host, refreshToken);
        }
        
        public async Task<SalesforceResponse> QueryFirstAsync(string q, string host, string refreshToken)
        {
            var response = await ExecSoqlAsync(q, host, refreshToken);
            var salesforceResponse = ConvertToSalesforceResponse(response);
            if (salesforceResponse.IsSuccess)
            {
                var queryResponse = JsonConvert.DeserializeObject<QueryResponse>(response);
                queryResponse = RemoveRecordAttributes(queryResponse);
                salesforceResponse.Payload = ConvertToSalesforceFieldValues(queryResponse?.records.FirstOrDefault());
            }
            return salesforceResponse;
        }

        public async Task<SalesforceResponse> SearchFirstAsync(SearchInput input, string host, string refreshToken)
        {
            var q = BuildSoql(input);
            return await QueryFirstAsync(q, host, refreshToken);
        }

        private List<SalesforceObject> ConvertToSalesforceObjects(SobjectResponse sobjectResponse)
        {
            List<SalesforceObject> salesforceObjects = new List<SalesforceObject>();

            if (sobjectResponse != null
                && sobjectResponse.sobjects != null)
            {
                var sobjects = sobjectResponse.sobjects.Where(f => f.createable == true);
                foreach (var sobject in sobjects)
                {
                    salesforceObjects.Add(ConvertToSalesforceObject(sobject));
                }
            }

            return salesforceObjects;
        }

        private SalesforceObject ConvertToSalesforceObject(Sobject sobject)
        {
            return new SalesforceObject()
            {
                Label = sobject.label,
                Name = sobject.name,
            };
        }

        private List<SalesforceField> ConvertToSalesforceFields(SobjectDescribeResponse sobjectDescribeResponse)
        {
            List<SalesforceField> salesforceFields = new List<SalesforceField>();

            if (sobjectDescribeResponse != null
                && sobjectDescribeResponse.fields != null)
            {
                foreach (var field in sobjectDescribeResponse.fields)
                {
                    salesforceFields.Add(ConvertToSalesforceField(field));
                }
            }

            return salesforceFields;
        }

        private SalesforceField ConvertToSalesforceField(Field field)
        {
            return new SalesforceField()
            {
                Label = field.label,
                Name = field.name,
                Length = field.length,
                Required = IsRequired(field.nillable, field.defaultedOnCreate),
                Type = field.type,
                References = field.referenceTo,
                Choices = ConvertToChoices(field.picklistValues),
                Createable = field.createable,
                DefaultedOnCreate = field.defaultedOnCreate,
            };
        }

        private bool IsRequired(bool defaultedOnCreate, bool nillable)
        {
            if (!nillable
                && defaultedOnCreate)
            {
                return false;
            }

            return !nillable;
        }

        private Choice[] ConvertToChoices(Picklistvalue[] picklistvalues)
        {
            Choice[] choices = null;
            if (picklistvalues != null)
            {
                choices = picklistvalues.Where(f => f.active).Select(a => new Choice()
                {
                    Label = string.IsNullOrEmpty(a.label) ? a.value : a.label,
                    Value = a.value,
                }).ToArray();
            }

            return choices;
        }

        private SalesforceResponse ConvertToSalesforceResponse(string response)
        {
            SalesforceResponse salesforceResponse = new SalesforceResponse()
            {
                IsSuccess = true,
            };

            if (!string.IsNullOrEmpty(response)
                && response.StartsWith('[')
                && response.Contains("errorCode"))
            {
                salesforceResponse.IsSuccess = false;
                var error = JsonConvert.DeserializeObject<List<SalesforceErrorInfo>>(response)?.FirstOrDefault();
                salesforceResponse.ErrorMessage = error?.message;
                salesforceResponse.ErrorCode = error?.errorCode;
                _logger.LogWarning(response);
            }

            return salesforceResponse;
        }

        private async Task<string> CreateAsync(string sobject, string content, string host, string refreshToken)
        {
            var response = await _webRequestService.SendAsync(host, $"/services/data/v52.0/sobjects/{sobject}", refreshToken, Enum.EnumHttpMethod.Post, content);
            return response;
        }

        private async Task<string> UpdateAsync(string sobject, string id, string content, string host, string refreshToken)
        {
            var response = await _webRequestService.SendAsync(host, $"/services/data/v52.0/sobjects/{sobject}/{id}", refreshToken, Enum.EnumHttpMethod.Patch, content);
            return response;
        }

        private string GetDupResourceId(string response, string sobject, SalesforceResponse salesforceResponse)
        {
            string url = string.Empty;
            if (salesforceResponse.ErrorCode == "DUPLICATES_DETECTED")
            {
                var errors = JsonConvert.DeserializeObject<List<SalesforceErrorInfo>>(response);
                var duplicateResut = errors?.FirstOrDefault(f => f.duplicateResut != null && f.duplicateResut.duplicateRuleEntityType == sobject)?.duplicateResut;
                var matchResult = duplicateResut?.matchResults.FirstOrDefault();
                if (matchResult != null
                    && matchResult.matchRecords != null)
                {
                    var matchRecord = matchResult.matchRecords.FirstOrDefault();
                    url = matchRecord.record.Id;
                }
            }

            return url;
        }

        private QueryResponse RemoveRecordAttributes(QueryResponse queryResponse)
        {
            if (queryResponse != null
                && queryResponse.records != null)
            {
                foreach (var record in queryResponse.records)
                {
                    record.Remove("attributes");
                }
            }

            return queryResponse;
        }

        private async Task<string> ExecSoqlAsync(string q, string host, string refreshToken)
        {
            var response = await _webRequestService.SendAsync(host, $"/services/data/v52.0/query?q={q}", refreshToken, Enum.EnumHttpMethod.Get);
            return response;
        }

        private List<SalesforceFieldValue> ConvertToSalesforceFieldValues(Dictionary<string, object> record)
        {
            return record?.Select(a => new SalesforceFieldValue()
            {
                Name = a.Key,
                Value = a.Value?.ToString(),
            }).ToList();
        }

        private string BuildSoql(SearchInput input)
        {
            CheckSearchInput(input);
            if (string.IsNullOrEmpty(input.FieldToSearchBy))
            {
                return $"select fields(all) from {input.SalesforceObject} limit 10";
            }
            else
            {
                return $"select fields(all) from {input.SalesforceObject} where {input.FieldToSearchBy} = '{input.SearchValue}' limit 10";
            }            
        }

        private void CheckSearchInput(SearchInput input)
        {
            if (input == null)
            {
                throw new Exception("SearchInput required");
            }
            if (string.IsNullOrEmpty(input.SalesforceObject))
            {
                throw new Exception("SearchInput.SalesforceObject required");
            }
            if (!string.IsNullOrEmpty(input.FieldToSearchBy)
                && string.IsNullOrEmpty(input.SearchValue))
            {
                throw new Exception("when SearchInput.FieldToSearchBy have a value then SearchInput.SearchValue required");
            }
        }
    }
}
